<?php
/**
 * This file defines the {@see \BF\Config\ValueCollection} class.
 *
 * @author     Kado <ulfikado@gmail.com>
 * @category   Beluga PHP5.3+ Framework
 * @package    BF\Core
 * @since      2015-01-28 14:29
 * @subpackage Config
 * @version    0.1
 */

namespace BF\Config
{

   /**
    * A associative Collection of {@see \BF\Config\Value} elements.
    *
    * @since      v0.1
    */
   class ValueCollection implements \ArrayAccess, \Serializable, \Iterator, \Countable
   {

      # <editor-fold defaultstate="collapsed" desc=" - - -   P R I V A T E   F I E L D S   - - - - - - - - - - - - - - - - - - - - - -">

      private $data;
      private $keys;
      private $position = 0;

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - >   P U B L I C   C O N S T R U C T O R   - - - - - - - - - - - - - - - - - -">

      /**
       * Inits a new instance.
       */
      public function __construct()
      {
          $this->data = array();
          $this->keys = array();
      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc=" - - -   P U B L I C   M E T H O D S   - - - - - - - - - - - - - - - - - - - - - -">

      /**
       * @param  string $name
       * @param  mixed  $defaultValue
       * @return mixed
       */
      public function getValue( $name, $defaultValue = null )
      {
          if ( !isset( $this->data[$name] ) ) return $defaultValue;
          return $this->data[$name];
      }

      public function toArray()
      {
          return $this->data;
      }

      # <editor-fold defaultstate="collapsed" desc="I M P L E M E N T   'ArrayAccess'">

      /**
       * Whether a offset exists.
       *
       * @param  mixed $offset An offset to check for.
       * @return boolean       <b>TRUE</b> on success or <b>FALSE</b> on failure. The return value will be casted to
       *                       boolean if non-boolean was returned.
       * @link   http://php.net/manual/en/arrayaccess.offsetexists.php
       */
      public function offsetExists( $offset )
      {
         return isset( $this->data[ $offset ] );
      }

      /**
       * Offset to retrieve
       *
       * @param  string $offset
       * @return \BF\Config\Value
       * @link   http://php.net/manual/en/arrayaccess.offsetget.php
       */
      public function offsetGet( $offset )
      {
         return $this->data[ $offset ];
      }

      /**
       * Offset to set.
       *
       * @param  mixed            $offset The offset to assign the value to.
       * @param  \BF\Config\Value $value  The value to set.
       * @return void
       * @throws \BF\ArgumentException
       * @link   http://php.net/manual/en/arrayaccess.offsetset.php
       */
      public function offsetSet( $offset, $value )
      {
         if ( \is_null( $offset ) || ! \is_string( $offset ) )
         {
            throw new \BF\ArgumentException( 'offset', $offset, 'Illegal config value name-definition.' );
         }
         if ( $offset != $value->name )
         {
            throw new \BF\ArgumentException( 'offset', $offset, 'Inconsistent config value name definition.' );
         }
         if ( isset( $this->data[ $offset ] ) )
         {
            if ( \is_null( $value ) )
            {
               unset ( $this->data[ $offset ] );
               $this->keys = \array_keys( $this->data );
               return;
            }
            if ( ! ( $value instanceof \BF\Config\Value ) )
            {
               $this->data[ $offset ]->value = $value;
               $this->keys = \array_keys( $this->data );
               return;
            }
            $this->data[ $offset ] = $value;
            $this->keys = \array_keys( $this->data );
            return;
         }
         if ( ! ( $value instanceof \BF\Config\Value ) )
         {
            if ( \is_null( $value ) )
            {
               throw new \BF\ArgumentException(
                  'value', $value, \sprintf( 'Can not assign NULL value to config value "%s".', $offset )
               );
            }
         }
         $this->data[ $offset ] = $value;
         $this->keys = \array_keys( $this->data );
      }

      public function offsetUnset( $offset )
      {
         unset ( $this->data[ $offset ] );
         $this->keys = \array_keys( $this->data );
      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc="I M P L E M E N T   'Serializable'">

      public function serialize()
      {
         return \serialize( $this->data );
      }

      public function unserialize( $serialized )
      {
         $array = array();
         $this->keys = array();
         try { $array = \unserialize( $serialized ); }
         catch ( \Exception $ex )
         {
            $ex = null;
            throw new \BF\ArgumentException(
               'serialized', $serialized,
               'Unable to deserialize data if data do not use not a valid php serialization format!'
            );
         }
         if ( ! \is_array( $array ) )
         {
            throw new \BF\ArgumentException(
               'serialized', $serialized,
               'Unable to deserialize config value data if data do not match required format!'
            );
         }
         $this->data = array();
         foreach ( $array as $row )
         {
             if ( ! ( $row instanceof \BF\Config\Value ) ) { continue; }
             $this->data[ $row->name ] = $row;
         }
         $this->keys = \array_keys( $this->data );
      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc="I M P L E M E N T   'Iterator'">

      public function rewind()
      {
         $this->position = 0;
      }
      public function valid()
      {
         return $this->position < \count( $this->data );
      }
      public function key()
      {
          return $this->keys[ $this->position ];
      }
      public function current()
      {
          return $this->data[ $this->keys[ $this->position ] ];
      }
      public function next()
      {
          $this->position++;
      }

      # </editor-fold>

      # <editor-fold defaultstate="collapsed" desc="I M P L E M E N T   'Countable'">

      public function count()
      {
         return count( $this->data );
      }

      # </editor-fold>

      # </editor-fold>

   }

}

